<?php

/**
 * Created by PhpStorm.
 * User: kejing.song
 * Email: 597481334@qq.com
 * Date: 2015/7/3
 * Time: 11:32
 */

namespace ClassLibrary;

use think\exception\ErrorException;

/**
 * Class ClFile(文件类库)
 * @package Common\Common
 */
class ClFile {
    /**
     * 无限新建文件夹，支持linux和windows目录
     * @param string $absolute_file_name 待创建的文件，例如：C:\workspace\PhpStorm\CC\WebSite\Application\Runtime\Logs/Home//DDD/14_08_07.log
     * @param bool $file_exists 传入的是否是文件，如果是文件则不进行文件、文件夹的自动判断
     * @return string
     */
    public static function dirCreate($absolute_file_name, $file_exists = false) {
        $file_name = trim(str_replace('\\', '/', $absolute_file_name), '/');
        $dir_array = explode('/', $file_name);
        if (ClSystem::isWin()) {
            $dir_str_pre = '';
        } else {
            $dir_str_pre = '/';
        }
        if (empty($dir_array[0])) {
            // 去除为空的数据
            array_shift($dir_array);
        }
        // 判断最后一个是文件还是文件夹
        if ($file_exists) {
            $min_limit = 1;
        } else {
            $min_limit = empty(self::getSuffix($file_name)) ? 0 : 1;
        }
        if ($min_limit > 0) {
            array_pop($dir_array);
        }
        // 赋值
        $temp_dir_array = $dir_array;
        while (is_array($temp_dir_array) && count($temp_dir_array) > $min_limit) {
            $dir_str = $dir_str_pre . implode('/', $temp_dir_array);
            if (is_dir($dir_str)) {
                break;
            }
            array_pop($temp_dir_array);
        }
        // 去除相同目录
        $dir_array = $dir_str_pre . implode('/', $dir_array);
        $dir_array = str_replace($dir_str, '', $dir_array);
        $dir_array = explode('/', trim($dir_array, '/'));
        // 第一个目录不判断
        while (is_array($dir_array) && !empty($dir_array)) {
            $dir_str .= '/' . $dir_array[0];
            if (!is_dir($dir_str)) {
                mkdir($dir_str, 0777);
                if (self::checkChmod($dir_str, 0777)) {
                    // 修改权限，root用户创建可能是0755
                    chmod($dir_str, 0777);
                }
            }
            array_shift($dir_array);
        }
        return $dir_str;
    }

    /**
     * 判断文件、文件夹权限
     * @param $file_path
     * @param int $target_chmod
     * @return bool
     */
    public static function checkChmod($file_path, $target_chmod = 0777) {
        $mod = substr(base_convert(@fileperms($file_path), 10, 8), -4);
        return ($mod == $target_chmod) || ($mod == strval($target_chmod));
    }

    /**
     * 获取当前文件夹下面所有的文件夹
     * @param $dir
     * @return array
     */
    public static function dirGet($dir) {
        $data = array();
        if (is_dir($dir)) {
            $dp = dir($dir);
            while ($file = $dp->read()) {
                if ($file != '.' && $file != '..') {
                    if (is_dir($dir . '/' . $file)) {
                        $data[] = $dir . '/' . $file;
                    }
                }
            }
        }
        return $data;
    }

    /**
     * 获取父类文件夹
     * @param string $dir
     * @return string
     */
    public static function dirGetFather($dir) {
        return dirname($dir);
    }

    /**
     * 获取文件夹下面的所有文件
     * @param string $dir 文件夹目录绝对地址
     * @param array $file_types :文件类型array('.pdf', '.doc')
     * @param array $ignore_dir_or_file : 忽略的文件或文件夹
     * @return array
     */
    public static function dirGetFiles($dir, $file_types = [], $ignore_dir_or_file = []) {
        clearstatcache();
        foreach (['.', '..'] as $each) {
            if (!in_array($each, $ignore_dir_or_file)) {
                $ignore_dir_or_file[] = $each;
            }
        }
        $data = [];
        if (is_dir($dir)) {
            $files = scandir($dir);
            foreach ($files as $file) {
                if (in_array($file, $ignore_dir_or_file)) {
                    continue;
                }
                if (is_dir($dir . DIRECTORY_SEPARATOR . $file)) {
                    $data = array_merge($data, self::dirGetFiles($dir . DIRECTORY_SEPARATOR . $file, $file_types, $ignore_dir_or_file));
                } else {
                    if (empty($file_types)) {
                        $data[] = self::pathClear($dir . DIRECTORY_SEPARATOR . $file);
                    } else {
                        // 判断类型
                        if (in_array(self::getSuffix($file), $file_types)) {
                            $data[] = self::pathClear($dir . DIRECTORY_SEPARATOR . $file);
                        }
                    }
                }
            }
        } else if (is_file($dir)) {
            if (empty($file_types)) {
                if (!in_array($dir, $ignore_dir_or_file)) {
                    $data[] = self::pathClear($dir);
                }
            } else {
                // 判断类型
                if (in_array(self::getSuffix($dir), $file_types) && !in_array($dir, $ignore_dir_or_file)) {
                    $data[] = self::pathClear($dir);
                }
            }
        }
        return $data;
    }

    /**
     * 路径整理
     * @param $path
     * @return mixed
     */
    public static function pathClear($path) {
        $path          = str_replace(['\\', '/'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $path);
        $separator_str = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
        while (strpos($path, $separator_str)) {
            $path = str_replace($separator_str, DIRECTORY_SEPARATOR, $path);
        }
        return $path;
    }

    /**
     * 获取文件名
     * @param $file
     * @param bool|false $with_suffix 是否带有后缀
     * @return array|string
     */
    public static function getName($file, $with_suffix = false) {
        $file = trim($file);
        if (strpos($file, '%2F') !== false) {
            // 做一次转义
            $file = urldecode($file);
        }
        $file   = basename($file);
        $suffix = self::getSuffix($file);
        // 兼容url处理
        foreach (['?', '#', '='] as $tag) {
            if (strpos($file, $tag) !== false) {
                if (strpos($file, $tag) > strpos($file, $suffix)) {
                    $file = ClString::getBetween($file, '', $tag, false);
                } else {
                    $file = ClString::getBetween($file, $tag, '', false);
                }
            }
        }
        if (!$with_suffix) {
            return str_replace($suffix, '', $file);
        } else {
            return $file;
        }
    }

    /**
     * 获取文件后缀名
     * @param $file
     * @param bool $with_point 是否包含.
     * @return string
     */
    public static function getSuffix($file, $with_point = true) {
        $suffix = isset(pathinfo($file)['extension']) ? strtolower(pathinfo($file)['extension']) : '';
        if ($with_point && !empty($suffix)) {
            $suffix = '.' . $suffix;
        }
        return $suffix;
    }

    /**
     * 下载文件
     * @param string $file 被下载文件的绝对路径
     * @param string $name 用户看到的文件名
     * @return void
     */
    public static function downloadForClient($file, $name = '') {
        $fileName = $name ? $name : self::getName($file, true);
        $filePath = realpath($file);
        $fp       = fopen($filePath, 'rb');
        if (!$filePath || !$fp) {
            header('HTTP/1.1 404 Not Found');
            throw new \Exception('Error: 404 Not Found.(server file path error)');
        }
        // ie 版本 gb2312转码
        $encoded_filename = ClString::encoding($fileName, ClString::V_ENCODE_GB2312);
        header('HTTP/1.1 200 OK');
        header('Pragma: public');
        header('Expires: 0');
        header('Content-type: application/octet-stream');
        header('Content-Length: ' . filesize($filePath));
        header('Accept-Ranges: bytes');
        header('Accept-Length: ' . filesize($filePath));
        $ua = ClHttp::getBrowser();
        if (in_array($ua[0], ['Internet Explorer', 'Edge'])) {
            header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
        } else if ($ua[0] == 'Mozilla Firefox') {
            header("Content-Disposition: attachment; filename*=\"utf8''" . $fileName . '"');
        } else {
            header('Content-Disposition: attachment; filename="' . $fileName . '"');
        }
        // ob_end_clean(); <--有些情况可能需要调用此函数
        // 输出文件内容
        fpassthru($fp);
    }

    /**
     * 依据远程地址生成本地地址
     * @param $url
     * @return string
     */
    public static function getLocalAbsoluteUrlByRemoteUrl($url) {
        if (strpos($url, 'http') === false) {
            return $url;
        }
        if (ClVerify::hasChinese($url)) {
            return public_path(false) . '/upload' . str_replace(basename($url), ClString::toCrc32(basename($url)), parse_url($url)['path']) . '.' . (pathinfo($url)['extension']);
        } else {
            return public_path(false) . '/upload' . parse_url($url)['path'];
        }
    }

    /**
     * 获取本地相对地址
     * @param $url
     * @return mixed
     */
    public static function getLocalUrlByRemoteUrl($url) {
        return str_replace(public_path(false), '', self::getLocalAbsoluteUrlByRemoteUrl($url));
    }

    /**
     * 中文路径转换为英文
     * @param $path
     * @return array|mixed|string
     */
    public static function pathChineseToEnglish($path) {
        // 先转码
        $path = ClString::encoding($path, 'UTF-8');
        $path = str_replace('\\', '/', $path);
        $path = preg_replace('/\/+/', '/', $path);
        if (strpos($path, '/') === false) {
            $suffix = self::getSuffix($path);
            if (ClVerify::hasChinese($path)) {
                if (!empty($suffix)) {
                    return ClString::toCrc32($path) . $suffix;
                } else {
                    return ClString::toCrc32($path);
                }
            } else {
                return $path;
            }
        } else {
            $path = explode('/', $path);
            foreach ($path as $k => $v) {
                $path[$k] = self::pathChineseToEnglish($v);
            }
            return implode('/', $path);
        }
    }

    /**
     * 获取文件html类型
     * @param $filename
     * @return string
     */
    public static function getMimeType($filename) {
        $path_info = pathinfo($filename);
        switch ($path_info['extension']) {
            case 'htm':
                $mime_type = 'text/html';
                break;
            case 'html':
                $mime_type = 'text/html';
                break;
            case 'txt':
                $mime_type = 'text/plain';
                break;
            case 'cgi':
                $mime_type = 'text/plain';
                break;
            case 'php':
                $mime_type = 'text/plain';
                break;
            case 'css':
                $mime_type = 'text/css';
                break;
            case 'jpg':
                $mime_type = 'image/jpeg';
                break;
            case 'jpeg':
                $mime_type = 'image/jpeg';
                break;
            case 'jpe':
                $mime_type = 'image/jpeg';
                break;
            case 'gif':
                $mime_type = 'image/gif';
                break;
            case 'png':
                $mime_type = 'image/png';
                break;
            default:
                $mime_type = 'application/octet-stream';
                break;
        }
        return $mime_type;
    }

    /**
     * 处理客户端上�
     * @param string $file_save_dir 文件保存绝对路径或相对路径，如果是相对路径，则会自动拼接成绝对路径
     * @param string $block_name 文件存储的块名，主要目的用于分类，便于后期管理资源
     * @param string $form_name 文件上传表单名称
     * @return array
     * @author SongKeJing qq:597481334 mobile:159-5107-8050
     * @date 2020/11/2 15:13
     */
    public static function uploadDealClient($file_save_dir = '', $block_name = '', $form_name = 'file') {
        $file = request()->file($form_name);
        // 判断上传文件是否存在
        if (!($file && $file->isValid())) {
            throw new \Exception('上传文件不存在');
        }
        if (!empty($file_save_dir) && strpos($file_save_dir, public_path(false)) === false) {
            $file_save_dir = public_path(false) . '/' . ltrim($file_save_dir, '/');
        }
        $file_size = get_param('file_size', ClFieldVerify::instance()->fetchVerifies(), '文件大小', $file->getSize());
        $file_name = get_param('name', ClFieldVerify::instance()->fetchVerifies(), '上传文件名', $file->getUploadName());
        $root_path = sprintf(public_path(false) . '/upload/' . (empty($block_name) ? '' : $block_name . '/') . '%s/', date('Y/m/d'));
        if (!is_dir($root_path)) {
            ClFile::dirCreate($root_path);
        }
        $save_file = ($file_save_dir == '' ? $root_path : $file_save_dir) . self::getName($file_name) . '_' . date('His') . '_' . ClString::getShortOrderNumber(8, 2) . self::getSuffix($file_name);
        if (!is_dir($save_file)) {
            ClFile::dirCreate($save_file);
        }
        // 保存
        $file->move($save_file);
        return [
            'result'    => true,
            'msg'       => '上传成功',
            'file_name' => $file_name,
            'file_size' => $file_size,
            'file'      => str_replace(public_path(false), '', $save_file)
        ];
    }

    /**
     * 获取远程文件大小
     * @param $remote_file_url
     * @return int
     */
    public static function getRemoteFileSize($remote_file_url) {
        if (strpos($remote_file_url, '//') === 0) {
            $remote_file_url = 'http:' . $remote_file_url;
        }
        try {
            $header = get_headers($remote_file_url, true);
        } catch (\ErrorException $e) {
            $header = '';
        }
        if (empty($header) || !isset($header['Content-Length'])) {
            return 0;
        } else {
            return $header['Content-Length'];
        }
    }

    /**
     * 抓取远程文件（支持大文件）
     * @param string $remote_file_url 远程文件地址
     * @param string $local_absolute_file 本地文件绝对地址
     * @return bool|string 下载的文件绝对地址
     */
    public static function catchRemote($remote_file_url, $local_absolute_file = '') {
        clearstatcache();
        if (strpos($remote_file_url, '//') === 0) {
            $remote_file_url = 'http:' . $remote_file_url;
        }
        if (empty($local_absolute_file)) {
            // 本地存储地址
            $local_absolute_file = ClFile::getLocalAbsoluteUrlByRemoteUrl($remote_file_url);
        }
        // 创建文件夹
        self::dirCreate($local_absolute_file);
        $remote_file_size = self::getRemoteFileSize($remote_file_url);
        if (is_file($local_absolute_file)) {
            if (($remote_file_size > 0 && filesize($local_absolute_file) == $remote_file_size)) {
                return $local_absolute_file;
            } else {
                unlink($local_absolute_file);
            }
        } else {
            // 创建文件夹
            self::dirCreate($local_absolute_file, true);
        }
        // 设置超时时间
        @ini_set('default_socket_timeout', 2);
        // 下载文件
        try {
            if (strpos($remote_file_url, 'https') === 0) {
                // https文件需要忽略ssl
                $f_remote = fopen($remote_file_url, 'rb', false, stream_context_create([
                                                                                           'ssl' => [
                                                                                               'verify_peer'      => false,
                                                                                               'verify_peer_name' => false,
                                                                                           ]
                                                                                       ]));
            } else {
                $f_remote = fopen($remote_file_url, 'rb');
            }
        } catch (\ErrorException $e) {
            log_info('fopen error:', $remote_file_url);
            // 设置id
            return false;
        }
        $file_size  = 0;
        $local_temp = dirname($local_absolute_file) . '/temp';
        $f_local    = fopen($local_temp, 'w+');
        // 输出文件内容
        while (!feof($f_remote)) {
            $content = fread($f_remote, 8192);
            if (empty($content)) {
                log_info('file fetch:size 0', $remote_file_url);
                break;
            }
            fputs($f_local, $content);
            $file_size += 8192;
            if ($file_size > 0 && $file_size % (1024 * 1024) == 0) {
                log_info('file_size:', $file_size / (1024 * 1024) . 'M');
            }
        }
        fclose($f_local);
        fclose($f_remote);
        $file_size = filesize($local_temp);
        if (($remote_file_size > 0 && $file_size != $remote_file_size)) {
            // 文件大小不一致
            unlink($local_temp);
            // 设置id
        } else {
            // 重命名
            rename($local_temp, $local_absolute_file);
        }
        return $local_absolute_file;
    }

    /**
     * 格式化文件大小
     * @param $file_size
     * @return string
     * @date 2023/9/25 18:06
     */
    public static function formatSize($file_size) {
        if ($file_size > 1024 * 1024) {
            $file_size = number_format($file_size / 1024 / 1024, 2) . ' MB';
        } else if ($file_size > 1024) {
            $file_size = number_format($file_size / 1024, 2) . ' KB';
        } else {
            $file_size = $file_size . ' B';
        }
        return $file_size;
    }
}
